package gameAppLayer.stateMachine;

import eventManager.AppEvent;
import eventManager.EventPortal;
import eventManager.IEventListener;
import gameAppLayer.CombatInitData;
import gameAppLayer.ExternalData;
import gameAppLayer.GameSettings;
import gameAppLayer.stateMachine.combatState.CombatStateBuilder;
import gameAppLayer.stateMachine.menuState.MenuStateBuilder;
import gameAppLayer.stateMachine.strategyState.StrategyStateBuilder;
import gameAppLayer.stateMachine.testState.TestStateBuilder;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.jme3.input.InputManager;
import com.jme3.renderer.Camera;

import testClasses.misc.TestGameSettingsDefaults;

/*
 * Listens for events from Game or GUI, transitions between GameStates
 */
public class GameStateMachine implements IEventListener {
	
	private static final Logger logger = Logger.getLogger(GameStateMachine.class.getName());
	
	private GameState currentState;
	private String currentStateID;
	private HashMap<String, GameState> gameStates = new HashMap<String, GameState>();	
	private InputManager inputManager;
	private Camera cam;
	private ExternalData externalData;
//	public boolean updateEnabled = false;
	private ArrayList<GameState> activeStates = new ArrayList<GameState>();
	
	public GameStateMachine(
			InputManager inputManager, 
			Camera cam,
			ExternalData externalData) {
		
		this.inputManager = inputManager;
		this.cam = cam;
		this.externalData = externalData;
		registerListeners();
		
		logger.log(Level.INFO, "GameStateMachine: GameStateMachine created");		
	}
	
	//Initializes MENU state at the start
	public void start() {
		logger.log(Level.INFO, "start() in DEFAULT MENU state");
		
		currentState = new MenuStateBuilder().createGameState(
				inputManager, 
				cam);
		currentStateID = currentState.getGameStateID();
		currentState.initialize(this);
		gameStates.put(currentStateID, currentState);		
	}

	
	private GameState buildState(String nextState, AppEvent event) {
		logger.log(Level.INFO, "GameStateMachine: buildState() " + nextState.toString());
		GameState state;
				
		if (nextState.equalsIgnoreCase("MENU")) {
			//GameStateMenuEvent menuEvent = (GameStateMenuEvent) event;
			state = new MenuStateBuilder().createGameState(
					inputManager, 
					cam);			
		}
		else if (nextState.equalsIgnoreCase("STRATEGY")) {
			Object[] objs = event.getEventObjects();			
			GameSettings settings = (GameSettings) objs[0];
			
			state = new StrategyStateBuilder().createGameState(
					settings, 
					inputManager, 
					cam, 
					externalData);			
		}
		else if (nextState.equalsIgnoreCase("COMBAT")){
			Object[] objs = event.getEventObjects();
			CombatInitData combatData = (CombatInitData) objs[0];
						
			state = new CombatStateBuilder().createGameState(
					combatData, 
					inputManager, 
					cam);			
		}
		else /*if (nextState.equalsIgnoreCase("TEST"))*/ { //It's therefore a TEST state of some type
			Object[] objs = event.getEventObjects();
			
			if (objs.length==3) {
				String testName = (String) objs[0];
				GameSettings settings = (GameSettings) objs[1];
				CombatInitData initData = (CombatInitData) objs[2];
				
				state = new TestStateBuilder().createGameState(
						testName,
						settings,
						initData,
						inputManager,
						cam,
						externalData);
			}
			else {
				logger.log(Level.INFO, "buildState() " + nextState.toString() + " FAILED.  Not enough input arguments");
				state = null;
			}			
		}		
		
		logger.log(Level.INFO, "GameStateMachine: buildState() " + nextState.toString() + " COMPLETE");
		
		return state;
	}
	
	private boolean swapState(String nextState, AppEvent event)	{
		logger.log(Level.INFO, "GameStateMachine: swapState() to " + nextState.toString());
		/*
		 * Note: May have to figure out some way to pause or stop eventManager.  Prevent
		 * events from the closing state from making the transition to the new state, where
		 * they will not find their targets.
		 * 
		 * This is only a problem if the events being used have specific targets.  If no
		 * specific targets, then they can just be broadcast to nothing.
		 */
		if (currentState.clear(this)) {
			
			if (gameStates.containsKey(nextState)) {
				logger.log(Level.INFO, "GameStateMachine: swapState(), swapping to " + nextState.toString());
				currentState = gameStates.get(nextState);				
			}
			else {
				logger.log(Level.INFO, "GameStateMachine: swapState() " + nextState.toString() + " does not exist");
				currentState = buildState(nextState, event);
				gameStates.put(currentState.getGameStateID(), currentState);
			}			
			currentStateID = currentState.getGameStateID();			
		}
		
		if (currentState.initialize(this)) {
			return true;
		}
		else
			logger.log(Level.INFO, "GameStateMachine: swapState() " + currentStateID.toString() + " startUp() FAILED");
			return false;
	}
	
	public String getStateID() {
		return currentStateID;
	}

	public void handleEvent(AppEvent event) {
		
		//NOTE:  Implement eventStrategy framework if number of events increases much beyond current 4.
		
		if (event.getEventName().equalsIgnoreCase("GameStateToMenu")) {
			swapState("MENU", event);			
		}
		else if (event.getEventName().equalsIgnoreCase("GameStateToStrategy")) {
			swapState("STRATEGY", event);
		}
		else if (event.getEventName().equalsIgnoreCase("GameStateToCombat")) {
			swapState("COMBAT", event);
		}
		else if (event.getEventName().equalsIgnoreCase("GameStateDelete")) {
			Object[] objs = event.getEventObjects();
			String delEvent = (String) objs[0];			
			gameStates.remove(delEvent);			
		}
		else if (event.getEventName().equalsIgnoreCase("GameStateToTest")) {
			swapState("TEST", event);
		}
	}
	
	private void registerListeners() {
		EventPortal.addListener(this, "GameStateToMenu");
		EventPortal.addListener(this, "GameStateToStrategy");
		EventPortal.addListener(this, "GameStateToCombat");
		EventPortal.addListener(this, "GameStateDelete");
		EventPortal.addListener(this, "GameStateToTest");
	}
	
	public boolean deactivateState(GameState state) {
		state.disable();
		return activeStates.remove(state);		
	}
	
	public void update(float tpf) {
		
		for (GameState state: activeStates) {
			state.update(tpf);
		}
		
//		if (updateEnabled) {
//			currentState.update(tpf);
//		}
		
	}

	//QQQ:  Need to fully implement this
	public HashSet<String> getEventTypes() {
		// TODO Auto-generated method stub
		return null;
	}
}



///*
//* Always starts in Menu State, then you can select options/test states
//*/
//public GameStateMachine(
//		InputManager inputManager, 
//		Camera cam,
//		ExternalData externalData,
//		String startState) {
//	
//			this.inputManager = inputManager;
//	this.cam = cam;
//	this.externalData = externalData;
//	registerListeners();
//	
//	if (gameStates.containsKey(startState)) {		
//		currentState = gameStates.get(startState);
//		currentStateID = startState;
//		currentState.startUp(this);
//		
//		logger.log(Level.INFO, "GameStateMachine initialized in " + currentStateID.toString() + " state");
//	}
//	else {
//		//state does not exist, so build it
//		//TO DO:  Code to enable building of requested state from scratch
//		
//		if (startState.equalsIgnoreCase("Strategy")) {
//			TestGameSettingsDefaults testSettings = new TestGameSettingsDefaults(externalData);
//			GameSettings settings = testSettings.settings;
//			GameState state = new FStrategyState().createGameState(
//					settings, 
//					inputManager, 
//					cam,
//					externalData);
//			currentState = state;
//			currentStateID = state.stateID;
//			currentState.startUp(this);
//			logger.log(Level.INFO, "GameStateMachine built " + currentStateID.toString() + " state, initialized");
//		}
//		else {
//			start();
//		}
//	}
//}